package edu.uvic.TabViz.framework.primitives;

import java.nio.FloatBuffer;
import java.util.ArrayList;

import android.graphics.Color;
import android.graphics.Matrix;
import android.opengl.GLES20;
import edu.uvic.TabViz.framework.primitives.TextureManager.TextureInfo;


public class Material  {
	protected int muSpecularColorHandle;
	protected int muAmbientColorHandle;
	protected int muShininessHandle;

	protected float[] mSpecularColor;
	protected float[] mAmbientColor;
	protected float mShininess;
	protected static final String mVShader = 
			"uniform mat4 uMVPMatrix;\n" +
					"uniform mat3 uNMatrix;\n" +
					"uniform mat4 uMMatrix;\n" +
					"uniform mat4 uVMatrix;\n" +
					"uniform vec3 uLightPos;\n" +

			"attribute vec4 aPosition;\n" +
			"attribute vec3 aNormal;\n" +
			"attribute vec2 aTextureCoord;\n" +
			"attribute vec4 aColor;\n" +

			"varying vec2 vTextureCoord;\n" +
			"varying vec3 N, L, E, H;\n" +
			"varying vec4 vColor;\n" +

			"void main() {\n" +
			"	gl_Position = uMVPMatrix * aPosition;\n" +
			"	vTextureCoord = aTextureCoord;\n" +

			"	vec4 eyePosition = uMMatrix  * aPosition;\n" + 
			"	vec4 eyeLightPos = vec4(uLightPos, 1.0);\n" +
			"	N = normalize(uNMatrix * aNormal);\n" +
			"	L = normalize(eyeLightPos.xyz - eyePosition.xyz);\n" + 
			"	E = -normalize(eyePosition.xyz);\n" +
			"	H = normalize(L + E);\n" +
			"	vColor = aColor;\n" +
			"}";

	protected static final String mFShader = 
			"precision mediump float;\n" +

			"varying vec2 vTextureCoord;\n" +
			"varying vec3 N, L, E, H;\n" +
			"varying vec4 vColor;\n" +

			"uniform vec4 uSpecularColor;\n" +
			"uniform vec4 uAmbientColor;\n" +
			"uniform vec4 uAmbientIntensity;\n" + 
			"uniform sampler2D uTexture0;\n" +
			"uniform sampler2D normalTexture;\n" +
			"uniform float uShininess;\n" +
			"uniform bool uUseTexture;\n" +

			"void main() {\n" +
			"	vec3 Normal = normalize(N);\n" +
			"	vec3 Light  = normalize(L);\n" +
			"	vec3 Eye    = normalize(E);\n" +
			"	vec3 Half   = normalize(H);\n" +

			"	float Kd = max(dot(Normal, Light), 0.0);\n" + 
			"	float Ks = pow(max(dot(Half, Normal), 0.0), uShininess);\n" + 
			"	vec4 diffuse  = uUseTexture ? Kd * texture2D(uTexture0, vTextureCoord) : Kd * vColor;\n" + 
			"	vec4 specular = Ks * uSpecularColor;\n" + 
			"	vec4 ambient  = uAmbientIntensity * uAmbientColor;\n" + 
			"	gl_FragColor = ambient + diffuse + specular;\n" + 
			"}";
	protected int program;
	protected Light light;
	protected int aPositionHandle;
	protected int aColorHandle;
	protected int aTextureHandle;
	protected int aNormalHandle;
	protected int modelViewProjectionMatrixHandle;
	protected int cameraPositionHandle;
	protected int modelMatrixHandle;
	protected int viewMatrixHandle;
	protected int lightIntensityHandle;
	
	protected int aClipHandle;

	protected int useTextureHandle;
	protected boolean useColor = true;
	protected boolean usesCubeMap = false;
	
	protected int numTextures = 0;
	protected ArrayList<TextureInfo> mTextureInfoList;
	protected Point3D cameraPosition;

	protected float[] modelViewMatrix;
	protected float[] viewMatrix;

	protected int muLightPosHandle;
	protected int muNormalMatrixHandle;
	protected int muAmbientIntensityHandle;

	protected float[] mNormalMatrix;
	protected float[] mLightPos;
	protected float[] mTmp, mTmp2;
	protected float[] mAmbientIntensity;

	protected Matrix mTmpNormalMatrix = new Matrix();
	protected Matrix mTmpMvMatrix = new Matrix();
	
	String vertexShader, fragmentShader;
	
	public Material() {
		this(mVShader,mFShader);
		mSpecularColor = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
		mAmbientColor = new float[] { 0.2f, 0.2f, 0.2f, 1.0f };
		mShininess = 96.0f;
	}
	
	public Material(String vertexShader, String fragmentShader) {
		mTextureInfoList = new ArrayList<TextureManager.TextureInfo>();
		this.vertexShader = vertexShader;
		this.fragmentShader = fragmentShader;
		setShaders(vertexShader, fragmentShader);
		mNormalMatrix = new float[9];
		mTmp = new float[9];
		mTmp2 = new float[9];
		mLightPos = new float[3];
		mAmbientColor = new float[] {.2f, .2f, .2f, 1};
		mAmbientIntensity = new float[] { .3f, .3f, .3f, 1 };
	}

	public void setShaders(String vertexShader, String fragmentShader)
	{
		program = createProgram(vertexShader, fragmentShader);
		if(program == 0) return;
		aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
		aNormalHandle = GLES20.glGetAttribLocation(program, "aNormal");
		aColorHandle = GLES20.glGetAttribLocation(program, "aColor");
		aTextureHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");
		cameraPositionHandle = GLES20.glGetUniformLocation(program, "uCameraPosition");
		modelViewProjectionMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
		modelMatrixHandle = GLES20.glGetUniformLocation(program, "uMMatrix");
		viewMatrixHandle = GLES20.glGetUniformLocation(program, "uVMatrix");
		lightIntensityHandle = GLES20.glGetUniformLocation(program, "uLightPower");
		useTextureHandle = GLES20.glGetUniformLocation(program, "uUseTexture");
		aClipHandle = GLES20.glGetUniformLocation(program, "aClip");
		muLightPosHandle = GLES20.glGetUniformLocation(program, "uLightPos");
		muNormalMatrixHandle = GLES20.glGetUniformLocation(program, "uNMatrix");
		muAmbientColorHandle = GLES20.glGetUniformLocation(program, "uAmbientColor");
		muAmbientIntensityHandle = GLES20.glGetUniformLocation(program, "uAmbientIntensity");
		muSpecularColorHandle = GLES20.glGetUniformLocation(program, "uSpecularColor");
		muShininessHandle = GLES20.glGetUniformLocation(program, "uShininess");	
	}

	protected int loadShader(int shaderType, String source) {
		int shader = GLES20.glCreateShader(shaderType);
		if (shader != 0) {
			GLES20.glShaderSource(shader, source);
			GLES20.glCompileShader(shader);
			int[] compiled = new int[1];
			GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
			if (compiled[0] == 0) {
				GLES20.glDeleteShader(shader);
				shader = 0;
			}
		}
		return shader;
	}

	protected int createProgram(String vertexSource, String fragmentSource) {
		int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
		if (vertexShader == 0) {
			return 0;
		}

		int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
		if (pixelShader == 0) {
			return 0;
		}

		int program = GLES20.glCreateProgram();
		if (program != 0) {
			GLES20.glAttachShader(program, vertexShader);
			GLES20.glAttachShader(program, pixelShader);
			GLES20.glLinkProgram(program);

			int[] linkStatus = new int[1];
			GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
			if (linkStatus[0] != GLES20.GL_TRUE) {
				GLES20.glDeleteProgram(program);
				program = 0;
			}
		}
		return program;
	}

	public void useProgram() {
		GLES20.glUseProgram(program);
		GLES20.glUniform1i(useTextureHandle, useColor == false ? GLES20.GL_TRUE : GLES20.GL_FALSE);
		GLES20.glUniform4fv(muAmbientColorHandle, 1, mAmbientColor, 0);
		GLES20.glUniform4fv(muAmbientIntensityHandle, 1, mAmbientIntensity, 0);
		GLES20.glUniform4fv(muSpecularColorHandle, 1, mSpecularColor, 0);
		GLES20.glUniform1f(muShininessHandle, mShininess);
	}

	public void setVertices(FloatBuffer vertices) {
		vertices.position(0);
		GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false, 0, vertices);
		GLES20.glEnableVertexAttribArray(aPositionHandle);
	}

	public void setColors(FloatBuffer colors) {
		colors.position(0);
		GLES20.glVertexAttribPointer(aColorHandle, 4, GLES20.GL_FLOAT, false, 0, colors);
		GLES20.glEnableVertexAttribArray(aColorHandle);
	}

	public void setNormals(FloatBuffer normals) {
		if(aNormalHandle > -1)
		{
			normals.position(0);
			GLES20.glVertexAttribPointer(aNormalHandle, 3, GLES20.GL_FLOAT, false, 0, normals);
			GLES20.glEnableVertexAttribArray(aNormalHandle);
		}
	}
	
	public void setTextures(FloatBuffer textureBuffer, boolean hasCubemapTexture) {
		textureBuffer.position(0);
    	GLES20.glVertexAttribPointer(aTextureHandle, hasCubemapTexture ? 3 : 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
        GLES20.glEnableVertexAttribArray(aTextureHandle);
    }
	
	public void setModelViewProjectionMatrix(float[] modelViewProjectionMatrix) {
		GLES20.glUniformMatrix4fv(modelViewProjectionMatrixHandle, 1, false, modelViewProjectionMatrix, 0);
	}

	public void setLight(Light light)
	{
		if(light == null) return;
		this.light = light;
		if(lightIntensityHandle > -1)
			GLES20.glUniform1f(lightIntensityHandle, light.getIntensity());
		Light dirLight = (Light)light;
		mLightPos[0] = dirLight.getPosition().x;
		mLightPos[1] = dirLight.getPosition().y;
		mLightPos[2] = dirLight.getPosition().z;
		GLES20.glUniform3fv(muLightPosHandle, 1, mLightPos, 0);
	}

	public void setCamera(Camera camera) {
		cameraPosition = new Point3D(camera.getPosition());
		if(cameraPositionHandle > -1)
			GLES20.glUniform3fv(cameraPositionHandle, 1, cameraPosition.getArray(), 0);
	}

	public int getProgram() {
		return program;
	}

	public void setProgram(int program) {
		this.program = program;
	}

	public int getaPositionHandle() {
		return aPositionHandle;
	}

	public void setaPositionHandle(int aPositionHandle) {
		this.aPositionHandle = aPositionHandle;
	}

	public int getaColorHandle() {
		return aColorHandle;
	}

	public void setaColorHandle(int aColorHandle) {
		this.aColorHandle = aColorHandle;
	}

	public int getaNormalHandle() {
		return aNormalHandle;
	}

	public void setaNormalHandle(int aNormalHandle) {
		this.aNormalHandle = aNormalHandle;
	}

	public int getModelViewProjectionMatrixHandle() {
		return modelViewProjectionMatrixHandle;
	}

	public void setModelViewProjectionMatrixHandle(
			int modelViewProjectionMatrixHandle) {
		this.modelViewProjectionMatrixHandle = modelViewProjectionMatrixHandle;
	}

	public int getCameraPositionHandle() {
		return cameraPositionHandle;
	}

	public void setCameraPositionHandle(int cameraPositionHandle) {
		this.cameraPositionHandle = cameraPositionHandle;
	}

	public int getModelMatrixHandle() {
		return modelMatrixHandle;
	}

	public void setModelMatrixHandle(int modelMatrixHandle) {
		this.modelMatrixHandle = modelMatrixHandle;
	}

	public int getViewMatrixHandle() {
		return viewMatrixHandle;
	}

	public void setViewMatrixHandle(int viewMatrixHandle) {
		this.viewMatrixHandle = viewMatrixHandle;
	}

	public int getLightIntensityHandle() {
		return lightIntensityHandle;
	}

	public void setLightIntensityHandle(int lightIntensityHandle) {
		this.lightIntensityHandle = lightIntensityHandle;
	}

	public Point3D getCameraPosition() {
		return cameraPosition;
	}

	public void setCameraPosition(Point3D cameraPosition) {
		this.cameraPosition = cameraPosition;
	}

	public float[] getModelViewMatrix() {
		return modelViewMatrix;
	}

	public void setModelViewMatrix(float[] modelMatrix) {
		this.modelViewMatrix = modelMatrix;
		if(modelMatrixHandle > -1)
			GLES20.glUniformMatrix4fv(modelMatrixHandle, 1, false, modelViewMatrix, 0);
		mTmp2[0] = modelMatrix[0]; mTmp2[1] = modelMatrix[1]; mTmp2[2] = modelMatrix[2]; 
		mTmp2[3] = modelMatrix[4]; mTmp2[4] = modelMatrix[5]; mTmp2[5] = modelMatrix[6];
		mTmp2[6] = modelMatrix[8]; mTmp2[7] = modelMatrix[9]; mTmp2[8] = modelMatrix[10];

		mTmpMvMatrix.setValues(mTmp2);

		mTmpNormalMatrix.reset();
		mTmpMvMatrix.invert(mTmpNormalMatrix);

		mTmpNormalMatrix.getValues(mTmp);
		mTmp2[0] = mTmp[0]; mTmp2[1] = mTmp[3]; mTmp2[2] = mTmp[6]; 
		mTmp2[3] = mTmp[1]; mTmp2[4] = mTmp[4]; mTmp2[5] = mTmp[7];
		mTmp2[6] = mTmp[2]; mTmp2[7] = mTmp[5]; mTmp2[8] = mTmp[8];
		mTmpNormalMatrix.setValues(mTmp2);
		mTmpNormalMatrix.getValues(mNormalMatrix);

		GLES20.glUniformMatrix3fv(muNormalMatrixHandle, 1, false, mNormalMatrix, 0);
	}

	public Light getLight() {
		return light;
	}

	public void setViewMatrix(float[] viewMatrix) {
		this.viewMatrix = viewMatrix;
		if(viewMatrixHandle > -1)
			GLES20.glUniformMatrix4fv(viewMatrixHandle, 1, false, viewMatrix, 0);
	}
	
	public float[] getViewMatrix() {
		return viewMatrix;
	}


	public void bindTextures() {
    	int num = mTextureInfoList.size();
    	
        for(int i=0; i<num; i++)
        {
        	TextureInfo ti = mTextureInfoList.get(i);
        	int type = usesCubeMap ? GLES20.GL_TEXTURE_CUBE_MAP : GLES20.GL_TEXTURE_2D;
        	GLES20.glEnable(type);
            GLES20.glActiveTexture(ti.getTextureSlot());
            GLES20.glBindTexture(type, ti.getTextureId());
            GLES20.glUniform1i(ti.getUniformHandle(), ti.getTextureSlot() - GLES20.GL_TEXTURE0);
        }
    }
	
	public void addTexture(TextureInfo textureInfo) {
    	int count = mTextureInfoList.size();
    	String textureName = "uTexture";
    	
    	switch(textureInfo.getTextureType()) {
    	case BUMP:
    		textureName = "uNormalTexture";
    		break;
    	case FRAME_BUFFER:
    		textureName = "uFrameBufferTexture";
    		break;
    	case DEPTH_BUFFER:
    		textureName = "uDepthBufferTexture";
    		break;
    	default:
    		textureName += count;
    		break;
    	}
    	
        int textureHandle = GLES20.glGetUniformLocation(program, textureName);
		if(textureHandle == -1) {
			throw new RuntimeException("Could not get attrib location for " + textureName);
		}
        textureInfo.setUniformHandle(textureHandle);
        useColor = false;
        mTextureInfoList.add(textureInfo);
        numTextures++;
    }
	
	public void copyTexturesTo(Material shader) {
		int num = mTextureInfoList.size();
		
		for(int i=0; i<num; ++i)
			shader.addTexture(mTextureInfoList.get(i));
	}

	public void setClip(float[] fs) {
		GLES20.glUniform4fv(aClipHandle, 1,fs, 0);
		
	}
	public void setAmbientcolor(float[] color) {
		mAmbientColor = color;
	}

	public void setAmbientcolor(float r, float g, float b, float a) {
		setAmbientcolor(new float[] { r, g, b, a });
	}

	public void setAmbientcolor(int color) {
		setAmbientcolor(new float[] { Color.red(color), Color.green(color), Color.blue(color), Color.alpha(color) });
	}

	public void setAmbientIntensity(float[] intensity) {
		mAmbientIntensity = intensity;
	}

	public void setAmbientIntensity(float r, float g, float b, float a) {
		setAmbientIntensity(new float[] { r, g, b, a });
	}
}
